package com.homelink.android.explorerup.view;

import java.util.LinkedList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.os.Handler;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.Scroller;

import com.homelink.android.explorerup.R;
import com.homelink.android.explorerup.adapter.SelectedAdapter;
import com.homelink.android.explorerup.listener.OnSelectedChangeListener;
import com.homelink.android.explorerup.listener.OnSelectedScrollListener;

public class SelectedView extends View {
	private static final int SCROLLING_DURATION = 400;
	//private static final int MIN_DELTA_FOR_SCROLLING = 1;

	/** Current value & label text color **/
	private static final int VALUE_TEXT_COLOR = 0xF0000000;
	private static final int ITEMS_TEXT_COLOR = 0xFF000000;

	/** Top and buttom shadows colors **/
	private static final int[] SHADOWS_COLORS = new int[] { 0xFF111111,
			0x00AAAAAA, 0x00AAAAAA };

	// Additional items height
	private static final int ADDITIONAL_ITEM_HEIGHT = 15;
	private static final int TEXT_SIZE = 24;
	private static final int ITEM_OFFSET = TEXT_SIZE / 5;
	private static final int ADDITIONAL_ITEMS_SPACE = 10;

	private static final int LABEL_OFFSET = 8;
	private static final int PADDING = 10;
	private static final int DEF_VISIBLE_ITEMS = 5;
	// Messages
	private final int MESSAGE_SCROLL = 0;
	private final int MESSAGE_JUSTIFY = 1;

	private SelectedAdapter adapter = null;
	private int currentItem = 0;

	private int itemsWidth = 0;
	private int labelWidth = 0;
	private int visibleItems = DEF_VISIBLE_ITEMS;
	private int itemHeight = 0;

	private TextPaint itemsPaint;
	private TextPaint valuePaint;

	private StaticLayout itemsLayout;
	private StaticLayout labelLayout;
	private StaticLayout valueLayout;

	private String label;
	private Drawable centerDrawable;

	private GradientDrawable topShadow;
	private GradientDrawable bottomShadow;

	private boolean isScrollingPerformed;
	private int scrollingOffset;

	private GestureDetector gestureDetector;
	private Scroller scroller;
	private int lastScrollY;
	boolean isCyclic = false;
	private int currentItems;

	private List<OnSelectedChangeListener> changingListeners = new LinkedList<OnSelectedChangeListener>();
	private List<OnSelectedScrollListener> scrollingListeners = new LinkedList<OnSelectedScrollListener>();

	// gesture listener
	private SimpleOnGestureListener gestureListener = new SimpleOnGestureListener() {
	};

	// animation handler
	private Handler animationHandler = new Handler() {

	};

	public SelectedView(Context context) {
		super(context);
		initData(context);
	}

	public SelectedView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initData(context);
	}

	/**
	 * Constructor
	 */
	public SelectedView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initData(context);
	}

	public SelectedAdapter getAdapter() {
		return adapter;
	}

	public void setAdapter(SelectedAdapter a) {
		this.adapter = a;
		invalidateLayouts();
		invalidate();
	}

	public void setInterpolator(Interpolator interpolator) {
		scroller.forceFinished(true);
		scroller = new Scroller(getContext(), interpolator);
	}

	public int getVisibleItems() {
		return visibleItems;
	}

	public void setVisibleItems(int count) {
		visibleItems = count;
		invalidate();
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String newLabel) {
		if (label == null || !label.equals(newLabel)) {
			label = newLabel;
			labelLayout = null;
			invalidate();
		}
	}

	public void addChangingListener(OnSelectedChangeListener listener) {
		changingListeners.add(listener);
	}

	public void removeChangingListener(OnSelectedChangeListener l) {
		changingListeners.remove(l);
	}

	public void addScrollingListener(OnSelectedScrollListener listener) {
		scrollingListeners.add(listener);
	}

	public void removeScrollingListener(OnSelectedScrollListener listener) {
		scrollingListeners.remove(listener);
	}

	public int getCurrentItem() {
		return currentItems;
	}

	public void setCurrentItem(int index, boolean animated) {
		if (adapter == null || adapter.getItemsCount() == 0) {
			return;
		}

		if (index < 0 || index >= adapter.getItemsCount()) {
			if (isCyclic) {
				while (index < 0) {
					index += adapter.getItemsCount();
				}
				index %= adapter.getItemsCount();
			} else {
				return;
			}
		}

		if (index != currentItem) {
			if (animated) {
				scroll(index - currentItem, SCROLLING_DURATION);
			} else {
				invalidateLayouts();

				int old = currentItem;
				currentItem = index;
				notifyChangingListeners(old, currentItem);
				invalidate();
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		SelectedAdapter adapter = getAdapter();
		if (adapter == null) {
			return true;
		}
		if (!gestureDetector.onTouchEvent(event)
				&& event.getAction() == MotionEvent.ACTION_UP) {
			justify();
		}

		return true;
	}

	public boolean isCyclic() {
		return isCyclic;
	}

	public void setCyclic(boolean c) {
		this.isCyclic = c;
		invalidate();
		invalidateLayouts();
	}

	public void setCurrentItem(int index) {
		setCurrentItem(index, false);
	}

	public void scroll(int itemsToScroll, int time) {
		scroller.forceFinished(true);
		lastScrollY = scrollingOffset;
		int offset = itemsToScroll * getItemHeight();
		scroller.startScroll(0, lastScrollY, 0, offset - lastScrollY, time);

		setNextMessage(MESSAGE_SCROLL);
		startScrolling();

	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		int width = calculateLayoutWidth(widthSize, widthMode);
		int height = 0;

		if (heightMode == MeasureSpec.EXACTLY) {
			height = heightSize;
		} else {
			height = getDesiredHeight(itemsLayout);
			if (heightMode == MeasureSpec.AT_MOST) {
				height = Math.min(height, heightSize);
			}
		}
		setMeasuredDimension(width, height);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		if (itemsLayout == null) {
			if (itemsWidth == 0) {
				calculateLayoutWidth(getWidth(), MeasureSpec.EXACTLY);
			} else {
				createLayouts(itemsWidth, labelWidth);
			}
		}

		if (itemsWidth > 0) {
			canvas.save();
			canvas.translate(PADDING, -ITEM_OFFSET);
			drawItems(canvas);
			drawValue(canvas);
			canvas.restore();
		}

		drawCenterRect(canvas);
		drawShadows(canvas);
	}

	protected void notifyScrollingListenersAboutStart() {
		for (OnSelectedScrollListener l : scrollingListeners) {
			l.onScrollingStarted(this);
		}
	}

	protected void notifyScrollingListenersAbountEnd() {
		for (OnSelectedScrollListener l : scrollingListeners) {
			l.onScrollingFinished(this);
		}
	}

	protected void notifyChangingListeners(int oldValue, int newValue) {
		for (OnSelectedChangeListener l : changingListeners) {
			l.onChanged(this, oldValue, newValue);
		}
	}

	private void startScrolling() {
		if (!isScrollingPerformed) {
			isScrollingPerformed = true;
			notifyScrollingListenersAboutStart();
		}
	}

	private void finishScrolling() {
		if (isScrollingPerformed) {
			notifyScrollingListenersAbountEnd();
			isScrollingPerformed = false;
		}
		invalidateLayouts();
		invalidate();
	}

	private void drawShadows(Canvas canvas) {
		topShadow.setBounds(0, 0, getWidth(), getHeight() / visibleItems);
		topShadow.draw(canvas);

		bottomShadow.setBounds(0, getHeight() - getHeight() / visibleItems,
				getWidth(), getHeight());
		bottomShadow.draw(canvas);
	}

	private void drawValue(Canvas canvas) {
		valuePaint.setColor(VALUE_TEXT_COLOR);
		valuePaint.drawableState = getDrawableState();
		Rect bounds = new Rect();
		itemsLayout.getLineBounds(visibleItems / 2, bounds);
		if (labelLayout != null) {
			canvas.save();
			canvas.translate(itemsLayout.getWidth() + LABEL_OFFSET, bounds.top);
			labelLayout.draw(canvas);
			canvas.restore();
		}

		if (valueLayout != null) {
			canvas.save();
			canvas.translate(0, bounds.top + scrollingOffset);
			valueLayout.draw(canvas);
			canvas.restore();
		}
	}

	private void drawItems(Canvas canvas) {
		canvas.save();
		int top = itemsLayout.getLineTop(1);
		canvas.translate(0, -top + scrollingOffset);
		itemsPaint.setColor(ITEMS_TEXT_COLOR);
		itemsPaint.drawableState = getDrawableState();
		itemsLayout.draw(canvas);
		canvas.restore();
	}

	private void drawCenterRect(Canvas canvas) {
		int center = getHeight() / 2;
		int offset = getItemHeight() / 2;
		centerDrawable.setBounds(0, center - offset, getWidth(), center
				+ offset);
		centerDrawable.draw(canvas);
	}

	private void justify() {
		if (adapter == null) {
			return;
		}
		lastScrollY = 0;
		int offset = scrollingOffset;
		int itemHeight = getItemHeight();

	}

	private void initResourcesIfNecessary() {
		if (itemsPaint == null) {
			itemsPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
					| Paint.FAKE_BOLD_TEXT_FLAG);
			itemsPaint.setTextSize(TEXT_SIZE);
		}

		if (valuePaint == null) {
			valuePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
					| Paint.FAKE_BOLD_TEXT_FLAG | Paint.DEV_KERN_TEXT_FLAG);
			valuePaint.setTextSize(TEXT_SIZE);
			valuePaint.setShadowLayer(0.1f, 0, 0.1f, 0xFFC0C0C0);
		}

		if (centerDrawable == null) {
			centerDrawable = getContext().getResources().getDrawable(
					R.drawable.selected_view_val);
		}

		if (topShadow == null) {
			topShadow = new GradientDrawable(Orientation.TOP_BOTTOM,
					SHADOWS_COLORS);
		}

		if (bottomShadow == null) {
			bottomShadow = new GradientDrawable(Orientation.BOTTOM_TOP,
					SHADOWS_COLORS);
		}

		this.setBackgroundResource(R.drawable.selected_view_bg);
	}

	private int getDesiredHeight(Layout layout) {
		if (layout == null) {
			return 0;
		}
		int desire = getItemHeight() * visibleItems - ITEM_OFFSET * 2
				- ADDITIONAL_ITEM_HEIGHT;
		desire = Math.max(desire, getSuggestedMinimumHeight());
		return desire;
	}

	private String getTextItem(int index) {
		if (adapter == null || adapter.getItemsCount() == 0) {
			return null;
		}
		int count = adapter.getItemsCount();
		if ((index < 0 || index >= count) && !isCyclic) {
			return null;
		} else {
			while (index < 0) {
				index = count + index;
			}
		}

		index %= count;
		return adapter.getItem(index);
	}

	private String buildText(boolean useCurrentValue) {
		StringBuilder itemsText = new StringBuilder();
		int addItems = visibleItems / 2 + 1;
		for (int i = currentItem - addItems; i <= currentItem + addItems; i++) {
			if (useCurrentValue || i != currentItem) {
				String text = getTextItem(i);
				if (text != null) {
					itemsText.append(text);
				}
			}

			if (i < currentItem + addItems) {
				itemsText.append("\n");
			}
		}
		return itemsText.toString();
	}

	private int getMaxTextLength() {
		SelectedAdapter adapter = getAdapter();
		if (adapter == null) {
			return 0;
		}

		int adapterLength = adapter.getMaximumLength();
		if (adapterLength > 0) {
			return adapterLength;
		}
		String maxText = null;
		int addItems = visibleItems / 2;
		int i = Math.max(currentItem - addItems, 0);
		int minLen = Math.min(currentItem + visibleItems,
				adapter.getItemsCount());
		for (; i < minLen; i++) {
			String text = adapter.getItem(i);
			if (text != null
					&& (maxText == null || maxText.length() < text.length())) {
				maxText = text;
			}

		}
		return maxText != null ? maxText.length() : 0;
	}

	private int calculateLayoutWidth(int widthSize, int mode) {
		initResourcesIfNecessary();
		int width = widthSize;
		int maxLength = getMaxTextLength();
		if (maxLength > 0) {
			float textWidth = FloatMath.ceil(Layout.getDesiredWidth("0",
					itemsPaint));
			itemsWidth = (int) (maxLength * textWidth);
		} else {
			itemsWidth = 0;
		}
		itemsWidth += ADDITIONAL_ITEMS_SPACE;

		labelWidth = 0;
		if (label != null && label.length() > 0) {
			labelWidth = (int) FloatMath.ceil(Layout.getDesiredWidth(label,
					valuePaint));
		}
		boolean recalculate = false;
		if (mode == MeasureSpec.EXACTLY) {
			width = widthSize;
			recalculate = true;
		} else {
			width = itemsWidth + labelWidth + 2 * PADDING;
			if (labelWidth > 0) {
				width += LABEL_OFFSET;
			}

			width = Math.max(width, getSuggestedMinimumWidth());

			if (mode == MeasureSpec.AT_MOST && widthSize < width) {
				width = widthSize;
				recalculate = true;
			}
		}

		if (recalculate) {
			int pureWidth = width - LABEL_OFFSET - 2 * PADDING;
			if (pureWidth <= 0) {
				itemsWidth = labelWidth = 0;
			}

			if (labelWidth > 0) {
				double newWidthItems = (double) itemsWidth * pureWidth
						/ (itemsWidth + labelWidth);
				itemsWidth = (int) newWidthItems;
				labelWidth = pureWidth - itemsWidth;
			} else {
				itemsWidth = pureWidth + LABEL_OFFSET;
			}
		}

		if (itemsWidth > 0) {
			createLayouts(itemsWidth, labelWidth);
		}
		return width;

	}

	private void createLayouts(int widthItems, int widthLabel) {
		if (itemsLayout == null || itemsLayout.getWidth() > widthItems) {
			Alignment align = widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE
					: Layout.Alignment.ALIGN_CENTER;
			itemsLayout = new StaticLayout(buildText(isScrollingPerformed),
					itemsPaint, widthItems, align, 1, ADDITIONAL_ITEM_HEIGHT,
					false);
		} else {
			itemsLayout.increaseWidthTo(widthItems);
		}

		if (!isScrollingPerformed
				&& (valueLayout == null || valueLayout.getWidth() > widthItems)) {
			String text = getAdapter() != null ? getAdapter().getItem(
					currentItem) : "";
			Alignment align = widthLabel > 0 ? Layout.Alignment.ALIGN_OPPOSITE
					: Layout.Alignment.ALIGN_CENTER;
			valueLayout = new StaticLayout(text, valuePaint, widthItems, align,
					1, ADDITIONAL_ITEM_HEIGHT, false);

		} else if (isScrollingPerformed) {
			valueLayout = null;
		} else {
			valueLayout.increaseWidthTo(widthItems);
		}

		if (widthLabel > 0) {
			if (labelLayout == null || labelLayout.getWidth() > widthLabel) {
				labelLayout = new StaticLayout(label, valuePaint, widthLabel,
						Layout.Alignment.ALIGN_NORMAL, 1,
						ADDITIONAL_ITEM_HEIGHT, false);

			} else {
				labelLayout.increaseWidthTo(widthLabel);
			}
		}
	}

	private void setNextMessage(int message) {
		clearMessages();
		animationHandler.sendEmptyMessage(message);
	}

	private void clearMessages() {
		animationHandler.removeMessages(MESSAGE_SCROLL);
		animationHandler.removeMessages(MESSAGE_JUSTIFY);
	}

	private int getItemHeight() {
		if (itemHeight != 0) {
			return itemHeight;
		} else if (itemsLayout != null && itemsLayout.getLineCount() > 2) {
			itemHeight = itemsLayout.getLineTop(2) - itemsLayout.getLineTop(1);
			return itemHeight;
		}

		return getHeight() / visibleItems;
	}

	private void invalidateLayouts() {
		itemsLayout = null;
		valueLayout = null;
		scrollingOffset = 0;
	}

	private void initData(Context ctx) {
		gestureDetector = new GestureDetector(gestureListener);
		gestureDetector.setIsLongpressEnabled(false);
		scroller = new Scroller(ctx);
	}

}
